﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;
using System.Net;
using System.IO;
using System.Reflection;
using System.Diagnostics;

namespace WebPALTT.Lib
{
    public class ThreadWorker
    {
        public ManualResetEvent ThreadsFinished;
        static int iThreadCount = 0;
        static int iMaxCount = 0;
        Assembly mCompiledAssembly;
        List<KeyValuePair<String, String>> mLog;
        List<ScenarioResults> mResults;

        public List<ScenarioResults> Results { get { return mResults; } }
        public KeyValuePair<String, String>[] Log { get { return mLog.ToArray(); } }

        public ThreadWorker(int maxCount, Assembly compiledAssembly)
        {
            iMaxCount = maxCount;
            iThreadCount = 0;
            mCompiledAssembly = compiledAssembly;
            mLog = new List<KeyValuePair<string, string>> { };
            mResults = new List<ScenarioResults> { };
        }

        /// <summary>
        /// Resets the current thread count
        /// </summary>
        public void Reset()
        {
            iThreadCount = 0;
        }

        public void SceneWorker(object obj)
        {
            Scenario scene = null;
            try
            {
                scene = (Scenario)((object[])obj).GetValue(0);                
                String className = (String)((object[])obj).GetValue(1);

                VirtualWorker worker = null;
                ManualResetEvent virtualThreadsFinished = new ManualResetEvent(false);
                switch (scene.RunConditions.RunMode)
                {
                    case RunMode.Concurrent:
                        worker = new VirtualWorker(scene.RunConditions.VirtualUsers, mCompiledAssembly);
                        worker.ThreadsFinished = virtualThreadsFinished;

                        for (int i = 0; i < scene.RunConditions.VirtualUsers; i++)
                        {
                            Scenario clone = ObjectCopier.Clone<Scenario>(scene);
                            ThreadPool.QueueUserWorkItem(new WaitCallback(worker.Worker), new object[] { clone, className, i + 1 });
                        }

                        virtualThreadsFinished.WaitOne(Timeout.Infinite, true);
                        break;
                    case RunMode.Sequential:
                        worker = new VirtualWorker(1, mCompiledAssembly);
                        worker.ThreadsFinished = virtualThreadsFinished;

                        for (int i = 0; i < scene.RunConditions.VirtualUsers; i++)
                        {
                            worker.Reset();
                            Scenario clone = ObjectCopier.Clone<Scenario>(scene);
                            ThreadPool.QueueUserWorkItem(new WaitCallback(worker.Worker), new object[] { clone, className, i + 1 });
                            virtualThreadsFinished.WaitOne(Timeout.Infinite, true);
                        }
                        break;
                    case RunMode.RampUp:
                        worker = new VirtualWorker(scene.RunConditions.StartingUsers, mCompiledAssembly);
                        worker.ThreadsFinished = virtualThreadsFinished;

                        DateTime testEnd = DateTime.Now.AddMinutes(scene.RunConditions.MaxTime);
                        int usersRun = scene.RunConditions.StartingUsers;
                        for (int i = 0; i < scene.RunConditions.StartingUsers; i++)
                        {
                            Scenario clone = ObjectCopier.Clone<Scenario>(scene);
                            ThreadPool.QueueUserWorkItem(new WaitCallback(worker.Worker), new object[] { clone, className, i + 1 });
                        }

                        while (true)
                        {
                            Thread.Sleep(scene.RunConditions.RampUpTimeSpan * 1000);

                            if (scene.RunConditions.StopOnUserCount && usersRun >= scene.RunConditions.VirtualUsers)
                                break;
                            else if (scene.RunConditions.StopOnMaxTime && DateTime.Compare(DateTime.Now, testEnd) >= 0)
                                break;
                            else if (!scene.RunConditions.StopOnUserCount && !scene.RunConditions.StopOnMaxTime)
                                break; // Error in Definition

                            usersRun += scene.RunConditions.RampUpUsers;
                            worker.IncreaseMaxCount(scene.RunConditions.RampUpUsers);
                            for (int i = 0; i < scene.RunConditions.RampUpUsers; i++)
                            {
                                Scenario clone = ObjectCopier.Clone<Scenario>(scene);
                                ThreadPool.QueueUserWorkItem(new WaitCallback(worker.Worker), new object[] { clone, className, i + 1 });
                            }
                        }
                        virtualThreadsFinished.WaitOne(Timeout.Infinite, true);
                        break;
                }
                mLog.AddRange(worker.Log);
                mResults.AddRange(worker.Results);
            }
            catch (Exception e)
            {
                mLog.Add(new KeyValuePair<string, string>("ERROR Creating Virtual Users: <" + (scene == null ? "null" : scene.Name) + ">",
                    e.Message + "\r\n\r\n" + e.StackTrace));
            }
            finally
            {
                Interlocked.Increment(ref iThreadCount);
                if (iThreadCount == iMaxCount)
                    ThreadsFinished.Set();
            }
        }
    }

    class VirtualWorker
    {
        public ManualResetEvent ThreadsFinished;
        static Mutex iMutex = new Mutex();
        static List<int> iThreadCount = new List<int> { };
        static List<int> iMaxCount = new List<int> { };
        int iThreadNumber;
        Assembly mCompiledAssembly;
        List<KeyValuePair<String, String>> mLog;
        List<ScenarioResults> mResults;

        public List<ScenarioResults> Results { get { return mResults; } }

        public KeyValuePair<String, String>[] Log { get { return mLog.ToArray(); } }

        /// <summary>
        /// Creates new VirtualWorker that will run each virtual user
        /// </summary>
        /// <param name="maxCount">The maximum number of threads expected</param>
        /// <param name="compiledAssembly">Reference to the assembly containing the user code to run</param>
        public VirtualWorker(int maxCount, Assembly compiledAssembly)
        {
            iMutex.WaitOne();
            iThreadNumber = iMaxCount.Count;
            iMaxCount.Add(maxCount);
            iThreadCount.Add(0);
            iMutex.ReleaseMutex();

            mCompiledAssembly = compiledAssembly;
            mLog = new List<KeyValuePair<string, string>> { };
            mResults = new List<ScenarioResults> { };
        }

        /// <summary>
        /// Resets the current thread count
        /// </summary>
        public void Reset()
        {
            ThreadsFinished.Reset();
            iMutex.WaitOne();
            iThreadCount[iThreadNumber] = 0;
            iMutex.ReleaseMutex();
        }

        public void IncreaseMaxCount(int increment)
        {
            iMutex.WaitOne();
            iMaxCount[iThreadNumber] = iMaxCount[iThreadNumber] + increment;
            iMutex.ReleaseMutex();
        }

        public void Worker(object obj)
        {
            Scenario scene = null;
            try
            {
                scene = (Scenario)((object[])obj).GetValue(0);
                scene.Actions.RefreshIndexes(true);
                String className = (String)((object[])obj).GetValue(1);
                Type _type = mCompiledAssembly.GetType("Script." + className);
                object script = mCompiledAssembly.CreateInstance("Script." + className);
                ScenarioResults resultScene = new ScenarioResults(scene.Key);
                resultScene.VirtualUserNo = (int)((object[])obj).GetValue(2);
                //
                ActionItem currentAction = null;
                //
                Stopwatch timer = new Stopwatch();
                timer.Start();
                //
                if(scene.Actions.Count > 0)
                    currentAction = scene.Actions[0];
                while (currentAction != null)
                {
                    ActionResults result = RunAction(currentAction, scene, resultScene, _type, script);
                    if (result != null)
                    {
                        int count = 0;
                        for (int i = 0; i < resultScene.Results.Count; i++)
                            if (Guid.Equals(resultScene.Results[i].Key, result.Key))
                                count++;
                        if (count > 0)
                            result.Name = result.Name + " (" + count + ")";
                        resultScene.Results.Add(result);
                    }
                    if (currentAction.HasChildren & currentAction.ExecutionStatus == ExecutionStatus.RunChildren)
                    {
                        currentAction = currentAction.Children.First();
                    }
                    else
                    {
                        ActionItemCollection list = null;
                        bool done = false;

                        while (!done)
                        {
                            if (currentAction.Parent == null)
                                list = scene.Actions;
                            else
                                list = currentAction.Parent.Children;

                            if ((currentAction.Index + 1) < list.Count)
                            {
                                currentAction = list[currentAction.Index + 1];
                                done = true;
                            }
                            else if (currentAction.Parent == null)
                            {
                                currentAction = null;
                                done = true;
                            }
                            else
                            {
                                currentAction = currentAction.Parent;
                                if (currentAction.ExecutionStatus != ExecutionStatus.Complete)
                                    done = true;
                            }
                        }
                    }
                }
                //
                //for (int i = 0; i < scene.Actions.Count; i++)
                //    result_actions.AddRange(ProcessAction(scene.Actions[i], scene, _type, script));

                timer.Stop();
                resultScene.Duration = timer.Elapsed;
                mResults.Add(resultScene);
            }
            catch (Exception e)
            {
                mLog.Add(new KeyValuePair<string, string>("ERROR Processing Scenario: <" + (scene == null ? "null" : scene.Name) + ">",
                    e.Message + "\r\n\r\n" + e.StackTrace));
            }
            finally
            {
                iMutex.WaitOne();
                iThreadCount[iThreadNumber] = iThreadCount[iThreadNumber] + 1;
                iMutex.ReleaseMutex();
                if (iThreadCount[iThreadNumber] == iMaxCount[iThreadNumber])
                    ThreadsFinished.Set();
            }
        }

        ActionResults RunAction(ActionItem action, Scenario scene, ScenarioResults resultScene, Type _type, object script)
        {
            Stopwatch timer = new Stopwatch();
            ActionResults result = new ActionResults(action.Name, action.Key, action.ActionType, action.CustomType);
            Dictionary<string, object> data = null;
            timer.Start();
            try
            {
                data = action.Execute(scene, resultScene, _type, script);
                timer.Stop();
                if (data == null)
                    return null; 
                foreach (KeyValuePair<string, object> kvp in data)
                    if (!result.Data.ContainsKey(kvp.Key))
                        result.Data.Add(kvp.Key, kvp.Value);
            }
            catch (TargetInvocationException e)
            {
                timer.Stop();
                result.Errors.Add(e.InnerException.GetType().ToString() + " - " + e.InnerException.Message + "\r\n\r\n" + e.InnerException.StackTrace);
            }
            catch (Exception e)
            {
                timer.Stop();
                result.Errors.Add(e.GetType().ToString() + " - " + e.Message + "\r\n\r\n" + e.StackTrace);
            }
            
            result.Results.Add("Duration", timer.Elapsed.TotalMilliseconds);

            return result;
        }
    }
}